package com.lightsaber.cut;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellReference;

import com.lightsaber.cut.entity.Component;
import com.lightsaber.cut.entity.ComponentPool;
import com.lightsaber.cut.entity.ComponentPoolType;
import com.lightsaber.cut.entity.Original;
import com.lightsaber.cut.entity.SortUtil;
import com.lightsaber.cut.func.Point;
import com.lightsaber.cut.func.Solutions;
import com.lightsaber.cut.func.TwoDimentionFunc;

/**
 * 
 * 
 * @author LiuJian
 */
public class App {

    // ------------------------- 核心参数--------------------start
    private static Integer A = null;
    private static Integer B = null;

    /**
     * 废料
     */
    private static Integer MAX_SURPLUS = null;
    /**
     * 允许最大备用余料
     */
    private static List<Integer> FIX_SURPLUSES = new ArrayList<Integer>();

    public static final int FIX_MOVE = 50;// 尺寸误差

    public static int POOL_GROUP_SIZE;
    public static int POOL_GROUP_SORT = 100;// 默认
    public static Boolean FIX_IN_GROUP = false;// 默认

    private static ComponentPool COMPONENTPOOL = null;
    // ------------------------- 核心参数--------------------end

    /**
     * 程序运行开始时间
     */
    private static final Date startTime = new Date();

    /**
     * 切割步骤： 分类 -> 分组 -> 分不同长度的整料
     * 
     * @param args
     */
    public static void main(String[] args) {

        try {
            List<ComponentPoolType> componentPoolTypes = readParams();

            File file = null;
            Point totalBetterPoint;
            for (ComponentPoolType componentPoolType : componentPoolTypes) {
                // 分类切割
                COMPONENTPOOL = componentPoolType.getComponentPool();
                totalBetterPoint = typeCut();
                file = saveResult(totalBetterPoint, componentPoolType.getTypeName());
            }

            // 打印结果
            printResult(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 分类切割，如圆管，竖管等
     * 
     * @return
     * @throws Exception
     */
    private static Point typeCut() throws Exception {
        Point totalBetterPoint = null;

        if (POOL_GROUP_SIZE > 0 && POOL_GROUP_SIZE < COMPONENTPOOL.getComponentPool().size()) {// 分组
            System.out
                    .println("一次分组COMPONENTPOOL.getComponentPool().zise = " + COMPONENTPOOL.getComponentPool().size());
            // 分组之前混淆顺序
            SortUtil.RandomSortComponent(COMPONENTPOOL.getComponentPool());
            List<Point> betterPoints = groupCut(COMPONENTPOOL);
            totalBetterPoint = new Point(0, 0, new ComponentPool(), new ArrayList<Original>());

            // step2: 综合各个分组的结果
            for (Point p : betterPoints) {
                updateBetterPoint(totalBetterPoint, p);
            }

            // -------------------------------------------------------------------------一次分组结果合并,二次分组
            // step3: 再次对总结果剩余料分组切割
            System.out.println("二次分组 totalBetterPoint.getLastPool().zise = "
                    + totalBetterPoint.getLastPool().getComponentPool().size());

            // 分组之前混淆顺序
            SortUtil.RandomSortComponent(totalBetterPoint.getLastPool().getComponentPool());
            betterPoints = groupCut(totalBetterPoint.getLastPool());
            totalBetterPoint.setLastPool(new ComponentPool());

            // step4: 再次对总结果剩余料分组切割后， 综合各个分组的结果
            for (Point p : betterPoints) {
                updateBetterPoint(totalBetterPoint, p);
            }

            // step3: 综合最后一组的结果
            System.out.println("两次分组后全部运行 totalBetterPoint.getLastPool().zise = "
                    + totalBetterPoint.getLastPool().getComponentPool().size());

            Point lastBetterPoint = control(true, totalBetterPoint.getLastPool());
            totalBetterPoint.setLastPool(new ComponentPool());

            updateBetterPoint(totalBetterPoint, lastBetterPoint);

        } else {// 不分组

            totalBetterPoint = control(true, COMPONENTPOOL);
        }

        System.out.println("y(min) = (-a/b)*x + l/b");
        System.out.println("y(max) = (-a/b)*x + l/(b-m)");
        System.out.println("x(max) = l/(a-m)\r\n");

        System.out.println("最优方案:");
        Collections.sort(totalBetterPoint.getOriginals());
        System.out.println(totalBetterPoint);

        // 返回结果
        return totalBetterPoint;
    }

    private static void updateBetterPoint(Point totalBetterPoint, Point betterPoint) {
        totalBetterPoint.setX(totalBetterPoint.getX() + betterPoint.getX());
        totalBetterPoint.setY(totalBetterPoint.getY() + betterPoint.getY());
        totalBetterPoint.getOriginals().addAll(betterPoint.getOriginals());
        totalBetterPoint.getLastPool().getComponentPool().addAll(betterPoint.getLastPool().getComponentPool());
    }

    private static List<Point> groupCut(ComponentPool groupComponentPool) throws Exception {
        List<Point> betterPoints = new ArrayList<Point>();

        // step1: 分组切割
        for (int i = 0; i < 100; i++) {
            ComponentPool componentPoolGroup = groupComponentPool.cloneGroup(i);
            // 最后跳出
            if (componentPoolGroup.getComponentPool().size() == 0) {
                break;
            }

            Point betterPoint = control(FIX_IN_GROUP, componentPoolGroup);
            betterPoints.add(betterPoint);

            // 最后跳出
            if (componentPoolGroup.getComponentPool().size() < POOL_GROUP_SIZE) {
                break;
            }
        }

        return betterPoints;
    }

    /**
     * 分段切割函数
     * 
     * @param cutFix
     * @param componentPoolSegment
     * @throws Exception
     */
    private static Point control(boolean cutFix, ComponentPool componentPoolSegment) throws Exception {
        System.out.println("componentPoolSegment.size=" + componentPoolSegment.getComponentPool().size());
        // 切之前先排序
        componentPoolSegment.sort();

        ComponentPool componentPoolCloneA = componentPoolSegment.clone();
        List<Original> originalA = componentPoolCloneA.cut(A, MAX_SURPLUS);
        int aMax = originalA.size();

        Solutions solutions = null;

        // 下1种料的时候, 方案中留下唯一点
        if (B == null) {
            solutions = new Solutions();
            Point p = new Point(aMax, 0);
            p.setLastPool(componentPoolCloneA);
            p.setOriginals(originalA);
            solutions.getSolutionAll().add(p);
        }

        // 下2种料的时候
        if (B != null) {
            ComponentPool componentPoolCloneB = componentPoolSegment.clone();
            int bMax = componentPoolCloneB.cut(B, MAX_SURPLUS).size();

            TwoDimentionFunc twoDimentionFunc = new TwoDimentionFunc(componentPoolSegment.getTotalLength(), A, B,
                    MAX_SURPLUS);
            solutions = twoDimentionFunc.getValidPoints(aMax, bMax);
            System.out.println(solutions.toString());

            Map<String, Point> pointMap = solutions.mapping();

            System.out.println(String.format("开始模拟实际切割(%d料优先)...................", A));
            for (int x = 0; x <= aMax; x++) {
                ComponentPool poolClone = componentPoolSegment.clone();

                List<Original> originalsA = poolClone.cut(A, MAX_SURPLUS, x);
                List<Original> originalsB = poolClone.cut(B, MAX_SURPLUS);

                Point p = pointMap.get(x + "-" + originalsB.size());
                if (p != null) {
                    originalsA.addAll(originalsB);

                    p.setOriginals(originalsA);
                    p.setLastPool(poolClone);
                }
            }
            System.out.println(solutions.toStringExperiment());

            System.out.println(String.format("开始模拟实际切割(%d料优先)...................", B));
            for (int y = 0; y <= bMax; y++) {
                ComponentPool poolClone = componentPoolSegment.clone();

                List<Original> originalsB = poolClone.cut(B, MAX_SURPLUS, y);
                List<Original> originalsA = poolClone.cut(A, MAX_SURPLUS);

                Point p = pointMap.get(originalsA.size() + "-" + y);
                if (p != null) {
                    // 前面的实验结果更理想, 跳过
                    if (p.getLastPool() != null && p.getLastPool().getTotalLength() <= poolClone.getTotalLength()) {
                        continue;
                    }

                    originalsA.addAll(originalsB);

                    p.setOriginals(originalsA);
                    p.setLastPool(poolClone);

                }
            }
            System.out.println(solutions.toStringExperiment());
        }

        List<Point> solutionAll = solutions.getSolutionAll();

        System.out.println("开始找最优点...................\r\n");
        Point pointBetter = null;
        for (Point point : solutionAll) {

            if (point.getOriginals() != null) {
                if (pointBetter == null) {// 第一个
                    pointBetter = point;
                    continue;
                }

                if (point.getLastPool().getTotalLength() < pointBetter.getLastPool().getTotalLength()) {
                    pointBetter = point;
                }
            }
        }

        if (cutFix) {
            System.out.println("开始切割余固定长度的尺寸...................");
            List<Original> originalsFixA = pointBetter.getLastPool().cutFix(A, FIX_SURPLUSES);
            pointBetter.setX(pointBetter.getX() + originalsFixA.size());
            pointBetter.getOriginals().addAll(originalsFixA);

            if (B != null) {
                List<Original> originalsFixB = pointBetter.getLastPool().cutFix(B, FIX_SURPLUSES);
                pointBetter.setY(pointBetter.getY() + originalsFixB.size());
                pointBetter.getOriginals().addAll(originalsFixB);
            }
        }

        return pointBetter;

    }

    /**
     * @throws Exception
     *             得到最后结果
     * 
     * @param pointBetter
     * @throws
     */
    private static void printResult(File file) throws Exception {
        Scanner sc = new Scanner(System.in);
        String speak = null;

        while (true) {
            System.out.println("是否打印(Y/N):");
            speak = sc.next();

            if ("Y".equalsIgnoreCase(speak)) {

                try {
                    Runtime.getRuntime().exec("notepad " + file.getAbsolutePath());
                } catch (IOException e) {
                    e.printStackTrace();
                }

                break;
            }

            if ("N".equalsIgnoreCase(speak)) {
                break;
            }
        }
    }

    /**
     * 保存最后结果到文件
     * 
     * @param pointBetter
     *            切割方案
     * @param typeCutTitle
     *            切割材料种类的标题
     * @return
     * @throws Exception
     */
    private static File saveResult(Point pointBetter, String typeCutTitle) throws Exception {
        File file = null;
        OutputStreamWriter writer = null;
        DateFormat df = new SimpleDateFormat("yy-MM-dd");
        DateFormat timef = new SimpleDateFormat("hh-mm-ss");
        String dirPath = "/切割方案";

        try {
            // Date validDate = df.parse("14-12-31");
            // if (startTime.compareTo(validDate) > 0) {
            // throw new Exception("无法创建文件");
            // }

            file = new File(dirPath);
            if (!file.exists()) {
                if (!file.mkdir()) {
                    throw new Exception("无法创建文件夹:" + file.getPath());
                }
            }

            file = new File(file, df.format(startTime));
            if (!file.exists()) {
                if (!file.mkdir()) {
                    throw new Exception("无法创建文件夹:" + file.getPath());
                }
            }

            file = new File(file, timef.format(startTime) + ".txt");
            writer = new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8");

            writer.write("------------------------------------------------------------\r\n");
            writer.write(typeCutTitle);
            writer.write(pointBetter.toString());
            writer.write("\r\n");

        } catch (Exception e) {
            throw new Exception(e.getMessage(), e);
        } finally {

            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                }
            }
        }

        System.out.println("成功保存到文件: " + file.getAbsolutePath());
        System.out.println("------------------------------------------------------\r\n\r\n");
        return file;
    }

    /**
     * 读取所有文件
     * 
     * @return
     * @throws Exception
     */
    private static List<ComponentPoolType> readParams() throws Exception {
        BufferedReader reader = null;
        List<ComponentPoolType> componentPoolTypes = new ArrayList<ComponentPoolType>();

        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream("app.properties"),"UTF-8"));

            String line = null;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("#") || "".equals(line)) {
                    continue;
                }

                if (line.startsWith("LS")) {
                    line = line.substring(line.indexOf("=") + 1);
                    String[] ls = line.split(",");
                    A = Integer.valueOf(ls[0].trim()) * 10;
                    if (ls.length > 1) {
                        B = Integer.valueOf(ls[1].trim()) * 10;
                    }

                    continue;
                }

                if (line.startsWith("MAX_SURPLUS")) {
                    line = line.substring(line.indexOf("=") + 1);
                    MAX_SURPLUS = Integer.valueOf(line.trim()) * 10;

                    continue;
                }

                if (line.startsWith("POOL_GROUP_SIZE")) {
                    line = line.substring(line.indexOf("=") + 1);
                    POOL_GROUP_SIZE = Integer.valueOf(line.trim());

                    continue;
                }

                if (line.startsWith("FIX_SURPLUSES")) {
                    line = line.substring(line.indexOf("=") + 1);
                    String[] fixSurpluses = line.split(",");
                    for (String fixSurplus : fixSurpluses) {
                        FIX_SURPLUSES.add(Integer.valueOf(fixSurplus.trim()) * 10);
                    }

                    continue;
                }

                if (line.startsWith("POOL_GROUP_SORT")) {
                    line = line.substring(line.indexOf("=") + 1);
                    POOL_GROUP_SORT = Integer.valueOf(line.trim()).intValue();
                }

                if (line.startsWith("FIX_IN_GROUP")) {
                    line = line.substring(line.indexOf("=") + 1);
                    FIX_IN_GROUP = Boolean.valueOf(line.trim());

                    continue;
                }

                // 解析txt中的尺寸
                if (line.startsWith("LENGTHS")) {
                    readComponentFromProperties(reader, componentPoolTypes);
                    break;
                }

                // 解析excel中的尺寸
                if (line.startsWith("FILE")) {
                    readComponentFromExcel(reader, componentPoolTypes);
                    break;
                }
            }

            System.out.println("成功读取配置文件 app.properties!\r\n");

        } catch (Exception e) {
            System.out.println("失败读取配置文件 app.properties!");
            throw e;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                }
            }
        }

        return componentPoolTypes;
    }

    private static void readComponentFromProperties(BufferedReader reader, List<ComponentPoolType> componentPoolTypes)
            throws NumberFormatException, IOException {
        ComponentPool componentPool = new ComponentPool();
        componentPoolTypes.add(new ComponentPoolType(componentPool, "普通切割"));

        String line = null;
        String sequenceStr = null;
        Integer sequence = null;

        while ((line = reader.readLine()) != null) {
            if (line.startsWith("#") || "".equals(line)) {
                continue;
            }

            // --------------------- 填充尺寸池 --------------------start
            int indexOfBracket = line.indexOf("(");

            sequenceStr = line.substring(0, indexOfBracket);
            sequence = Integer.valueOf(sequenceStr.trim());

            String[] lengths = line.substring(indexOfBracket + 1, line.indexOf(")")).split(",");
            Component component = null;

            for (String length : lengths) {
                Float lengthF = Float.valueOf(length.trim()) * 1000;

                component = new Component();
                component.setLength(lengthF.intValue());
                component.setSequence(sequence);
                componentPool.put(component);
            }
            // --------------------- 填充尺寸池 --------------------end
        }

        System.out.println(componentPool.toString());
    }

    private static void readComponentFromExcel(BufferedReader reader, List<ComponentPoolType> componentPoolTypes)
            throws Exception {
        String fileName = null;
        List<String> cutTypeNames = new ArrayList<String>();

        // 1. 读取excel名 和 切割类型名
        String line = null;
        while ((line = reader.readLine()) != null) {
            if (line.startsWith("#") || "".equals(line)) {
                continue;
            }

            if (line.startsWith("EXCEL")) {
                line = line.substring(line.indexOf("=") + 1);
                fileName = line.trim();
            }

            if (line.startsWith("CUT")) {
                int indexOfBracket = line.indexOf("(");
                line = line.substring(indexOfBracket + 1, line.indexOf(")"));
                cutTypeNames.add(line.trim());
            }
        }

        // 2. 初始化尺寸池
        Map<String, ComponentPool> typeName2Pool = new HashMap<String, ComponentPool>();
        for (String typeName : cutTypeNames) {
            ComponentPool componentPool = new ComponentPool();
            componentPoolTypes.add(new ComponentPoolType(componentPool, typeName));

            // 映射列名到pool
            String[] columnNmeArr = typeName.split(",");
            for (String columnNme : columnNmeArr) {
                typeName2Pool.put(columnNme.trim(), componentPool);
            }
        }

        // 3. 读取excel的尺寸到尺寸池
        FileInputStream is = null;
        try {
            // 创建一个文件输入流
            is = new FileInputStream(fileName);
            // 得到工作薄
            Workbook workbook = new HSSFWorkbook(is);
            FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
            Sheet sheet = workbook.getSheetAt(0);

            int startRowNm = 0;// 起始行
            int lastRowNm = sheet.getLastRowNum();
            int lastColNm = sheet.getRow(5).getLastCellNum();

            // 映射列名到列号
            Map<String, Integer> typeName2Column = new HashMap<String, Integer>();
            for (int rowN = 0; rowN < lastRowNm; rowN++) {

                for (int colN = 0; colN < lastColNm; colN++) {

                    Cell cell = sheet.getRow(rowN).getCell(colN);
                    if (cell == null || cell.getCellType() != Cell.CELL_TYPE_STRING) {
                        continue;
                    }

                    String cellValue = cell.getStringCellValue().trim();// 取值
                    if (typeName2Pool.containsKey(cellValue)) {
                        typeName2Column.put(cellValue, colN);
                    }
                }

                if (typeName2Column.keySet().size() == typeName2Pool.keySet().size()) {// 找到表头后返回
                    startRowNm = rowN;// 起始行
                    break;
                }
            }

            // 读入尺寸
            Component component = null;
            Integer length = null;
            Integer cnt = null;
            ComponentPool componentPool = null;

            for (int rowN = startRowNm + 1; rowN < lastRowNm; rowN++) {

                Iterator<String> columnIt = typeName2Column.keySet().iterator();

                while (columnIt.hasNext()) {
                    String key = columnIt.next();
                    int colN = typeName2Column.get(key);// 直接读取第几列

                    // 用公式读取cell
                    Cell lenCell = sheet.getRow(rowN).getCell(colN);
                    CellValue lenCellValue = evaluator.evaluate(lenCell);
                    Cell cntCell = sheet.getRow(rowN).getCell(colN + 1);
                    CellValue cntCellValue = evaluator.evaluate(cntCell);

                    if (lenCellValue == null || lenCellValue.getCellType() != Cell.CELL_TYPE_NUMERIC) {
                        return;// 完成所有任务
                    }

                    // -------------------------------装尺寸--------------------------start
                    componentPool = typeName2Pool.get(key);
                    length = Double.valueOf(lenCellValue.getNumberValue()).intValue();
                    cnt = Double.valueOf(cntCellValue.getNumberValue()).intValue();
                    for (int i = 0; i < cnt; i++) {
                        component = new Component();
                        component.setLength(length);
                        component.setSequence(rowN - startRowNm);
                        componentPool.put(component);
                    }
                    // -------------------------------装尺寸--------------------------end
                }
            }

        } catch (Exception e) {
            System.out.println("解析excel: " + fileName + " 出错!");
            throw e;
        } finally {
            if (is != null) {
                is.close();
            }
        }

    }
}
